﻿/*************************************************************************************
 *
 * 文 件 名：  Worker
 * 描    述：  命令下发服务，根据操作命令信息，生成下发命令，再提交到kafka，然后油数据接收服务下发到设备
 * 
 * 版    本：  V1.0
 * 创 建 者：  YSW
 * 创建时间：  2021-12-08 9:06:44
 * ======================================
 * 历史更新记录
 * 版本：V          修改时间：         修改人：
 * 修改内容：
 * ======================================
*************************************************************************************/

using CommonModel.Util;
using GXRTBTC.PublicClassLibrary;
using GXRTBTC.PublicClassLibrary.Field;
using GXRTBTC.PublicClassLibrary.Model;
using GXRTBTC.PublicClassLibrary.Util;
using HPSocket;
using HPSocket.Tcp;
using HPSocket.Udp;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NewLife;
using Newtonsoft.Json;
using RedisHelp;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GXRTBTC.CommandIssueService
{
    public class PowSend
    {
        public int sendType { get; set; } = 0;
        public byte[] command { get; set; }
        public string tipLog { get; set; }
    }

    /// <summary>
    /// 后台服务
    /// </summary>
    public class Worker : BackgroundService
    {
        #region 参数
        /// <summary>
        /// 当前节点名称
        /// 组成：节点所在IP+容器ID
        /// </summary>
        //public string nodeName { get; set; } = TransForm.GetContainerAddress() + "_" + Environment.MachineName;
        public string nodeName { get; set; } = TransForm.GetHostIPOrName() + "_" + Environment.MachineName;
        /// <summary>
        /// 日志
        /// </summary>
        private readonly ILogger mLogger;
        /// <summary>
        /// 配置
        /// </summary>
        private readonly IConfiguration mConfiguration;
        /// <summary>
        /// StackExchange.Redis
        /// </summary>
        private RedisHelper redis { get; set; }
        private RedisHelper redisPower { get; set; }
        private RedisHelper redisPowerData { get; set; }
        private RedisHelper redisTask { get; set; }

        private Object syncLock = new object();
        /// <summary>
        /// 接收包数量
        /// </summary>
        private long pkgsRcv { get; set; } = 0;
        /// <summary>
        /// 是否接收到数据
        /// </summary>
        private bool isRcvData { get; set; } = false;

        /// <summary>
        /// 接收数据总量
        /// </summary>
        private long numsRcv { get; set; } = 0;
        private long cmdSend { get; set; } = 0;
        /// <summary>
        /// 实时电源复位器的连接台站数量
        /// </summary>
        private int powerStationCount { get; set; } = 0;

        /// <summary>
        /// 接收数据缓存
        /// </summary>
        //private BlockingCollection<OperateModel> rcvCacheNormal { get; set; } = null;
        /// <summary>
        /// 加速缓存，和rcvCacheNormal区分开来，以免加速影响操作速度
        /// </summary>
        private BlockingCollection<OperateModel> rcvCacheQuick { get; set; } = null;
        //private BlockingCollection<OperateModel> rcvCacheTask { get; set; } = null;
        private long powerSendCount { get; set; } = 0;

        /// <summary>
        /// 配置信息
        /// </summary>
        private Dictionary<string, SchemaDocument> schemaDict { get; set; } = new Dictionary<string, SchemaDocument>();

        private string snmpGet { get; set; } = "10.71.31.220:4021";

        /// <summary>
        /// 最小线程池数量
        /// </summary>
        private int minPoolNum { get; set; } = 10;
        /// <summary>
        /// 最大线程池数量
        /// </summary>
        private int maxPoolNum { get; set; } = 100;

        private long timeOutNum { get; set; } = 0;

        /// <summary>
        /// 副本数，默认只有自身
        /// </summary>
        private int nodeNum { get; set; } = 1;
        /// <summary>
        /// 当前副本编号
        /// </summary>
        private int nodeNo { get; set; } = 0;

        /// <summary>
        /// 台站，工位号对应设备信息
        /// </summary>
        private Dictionary<string, Dictionary<string, DeviceInfo>> stationPositionDeviceInfos { get; set; } = new Dictionary<string, Dictionary<string, DeviceInfo>>();
        /// <summary>
        /// 工位号和频率对应
        /// </summary>
        public Dictionary<string, Dictionary<string, Dictionary<string, string>>> positionIdToFrqs { get; set; } = new();
        /// <summary>
        /// 模板信息
        /// </summary>
        private Dictionary<string, Dictionary<string, string>> issueTemplates { get; set; } = new Dictionary<string, Dictionary<string, string>>();
        /// <summary>
        /// 模板参数信息
        /// </summary>
        private Dictionary<string, Dictionary<string, string>> issuePara { get; set; } = new Dictionary<string, Dictionary<string, string>>();
        /// <summary>
        /// 设备下发模板信息
        /// </summary>
        private Dictionary<string, Dictionary<string, Dictionary<string, string>>> devIssueInfo { get; set; } = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>();
        /// <summary>
        /// 特殊处理的设备类型
        /// 目前只有0x0101的状态数据（cmd_type=1）读取时
        /// 加速间隔为300毫秒
        /// 默认是1000毫秒（1秒）
        /// </summary>
        private Dictionary<string, int> specDevice { get; set; } = new Dictionary<string, int>() { { "0x0101", 1 } };

        /// <summary>
        /// 配置集
        /// </summary>
        private Dictionary<string, string> paramInfo { get; set; } = new();

        /*******************电源复位器参数*************************/
        private string powerServer { get; set; } = "10.170.1.61";
        private string powerPort { get; set; } = "40002";

        private ITcpClient powerClient { get; set; } = null;
        /*******************电源复位器参数*************************/
        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="configuration"></param>
        public Worker(ILogger<Worker> logger, IConfiguration configuration)
        {
            this.mLogger = logger;
            this.mConfiguration = configuration;

            //给通用日志赋值
            CommonUtil.mLogger = logger;

            Init();
        }

        #region 初始化
        /// <summary>
        /// 初始化
        /// </summary>
        private void Init()
        {
            //设置线程池
            System.Threading.ThreadPool.SetMinThreads(minPoolNum, minPoolNum);
            System.Threading.ThreadPool.SetMaxThreads(maxPoolNum, maxPoolNum);

            try
            {
                //redis初始化
                string redisConnString = "";
#if DEBUG
                redisConnString = RsaEncrypt.DecryptFree(mConfiguration["RedisConnStringDebug"]);
#else
                redisConnString = RsaEncrypt.DecryptFree(mConfiguration["RedisConnString"]);
#endif
                redis = new RedisHelper(redisConnString, mLogger);
                redisTask = new RedisHelper(redisConnString, mLogger);
                redisPower = new RedisHelper(redisConnString, mLogger);
                redisPowerData = new RedisHelper(redisConnString, mLogger);
            }
            catch { }

            //从缓存读取设置
            paramInfo = redis.HashGetAllDict<string>("paramInfo");

            //rcvCacheNormal = new BlockingCollection<OperateModel>();
            rcvCacheQuick = new BlockingCollection<OperateModel>();
            //rcvCacheTask = new BlockingCollection<OperateModel>();

            powerServer = paramInfo.GetValueOrDefault("PowerResetAddr", "10.170.1.61");
            powerPort = paramInfo.GetValueOrDefault("PowerResetPort", "40002");

            //从redis读取缓存信息
            InitCacheInfo();
        }

        /// <summary>
        /// 初始化信息
        /// </summary>
        private void TimerInit()
        {
            Thread.Sleep(60 * 1000);
            while (true)
            {
                //redis缓存信息初始化
                InitCacheInfo();
                Thread.Sleep(60 * 1000);
            }
        }

        /// <summary>
        /// redis缓存信息初始化
        /// </summary>
        private void InitCacheInfo()
        {
            try
            {
                //台站工位号设备信息
                Dictionary<string, Dictionary<string, DeviceInfo>> tmpStationPositionDeviceInfos = redis.HashGetAllDict<Dictionary<string, DeviceInfo>>("station_position_deviceInfos");
                if (tmpStationPositionDeviceInfos != null && tmpStationPositionDeviceInfos.Count > 0)
                {
                    lock (stationPositionDeviceInfos)
                    {
                        stationPositionDeviceInfos = tmpStationPositionDeviceInfos;
                    }
                }

                //模板信息
                Dictionary<string, Dictionary<string, string>> tmpIssueTemplates = redis.HashGetAllDict<Dictionary<string, string>>("issueTemplates");
                if (tmpIssueTemplates != null && tmpIssueTemplates.Count > 0)
                {
                    lock (issueTemplates)
                    {
                        issueTemplates = tmpIssueTemplates;
                    }
                }

                //模板参数信息
                Dictionary<string, Dictionary<string, string>> tmpIssuePara = redis.HashGetAllDict<Dictionary<string, string>>("issueparas");
                if (tmpIssuePara != null && tmpIssuePara.Count > 0)
                {
                    lock (issuePara)
                    {
                        issuePara = tmpIssuePara;
                    }
                }

                //设备下发模板信息
                Dictionary<string, Dictionary<string, Dictionary<string, string>>> tmpDevIssueInfo = redis.HashGetAllDict<Dictionary<string, Dictionary<string, string>>>("devIssueInfo");
                if (tmpDevIssueInfo != null && tmpDevIssueInfo.Count > 0)
                {
                    lock (devIssueInfo)
                    {
                        devIssueInfo = tmpDevIssueInfo;
                    }
                }

                Dictionary<string, SchemaDocument> tmpSchemaDict = redis.HashGetAllDict<SchemaDocument>("pre_analysis_config");
                if (tmpSchemaDict != null && tmpSchemaDict.Count > 0)
                {
                    lock (schemaDict)
                    {
                        schemaDict = tmpSchemaDict.Where(d => d.Key.ToUpper().Equals("0X0708_SNMP_GETBASICMSG")).ToDictionary(d => d.Key, d => d.Value);
                    }
                }

                //工位号和频率对应
                Dictionary<string, Dictionary<string, Dictionary<string, string>>> tmpPositionIdToFrqs = redis.HashGetAllDict<Dictionary<string, Dictionary<string, string>>>("positionIdToFrq");
                if (tmpPositionIdToFrqs != null && tmpPositionIdToFrqs.Count > 0)
                {
                    lock (tmpPositionIdToFrqs)
                    {
                        positionIdToFrqs = tmpPositionIdToFrqs;
                    }
                }
            }
            catch (Exception e)
            {
                mLogger.LogInformation("更新获取台站信息缓存失败，" + e);
            }
        }
        #endregion

        #region 辅助方法
        /// <summary>
        /// 拼接日志插入sql
        /// </summary>
        /// <param name="operateModel"></param>
        /// <returns></returns>
        private string GetInsLogTaosSql(OperateModel operateModel)
        {
            StringBuilder sql = new StringBuilder();
            sql.Append("insert into t_operate_log(ts, position_id, stationCode, sevicesn, device, ip_addr, op_port, log_info, command, op_code, op_time) values(")
                .Append("'" + string.Format("{0:yyyy-MM-dd HH:mm:ss.ffffff}", DateTime.Now) + "'")
                .Append(",'" + operateModel.position_id + "'")
                .Append(",'" + operateModel.deviceInfo.stationCode + "'")
                .Append(",'" + operateModel.deviceInfo.seviceSN + "'")
                .Append(",'" + operateModel.deviceInfo.deviceType + "'")
                .Append(",'" + operateModel.deviceInfo.ip_addr + "'")
                .Append("," + operateModel.deviceInfo.port)
                .Append(",'" + operateModel.logInfo + "'")
                .Append(",'" + operateModel.cmd + "'")
                .Append(",'" + operateModel.op_code + "'")
                .Append(",'" + string.Format("{0:yyyy-MM-dd HH:mm:ss.ffffff}", operateModel.op_time) + "'")
                .Append(")");
            return sql.ToString();
        }

        /// <summary>
        /// 拼接日志插入sql
        /// </summary>
        /// <param name="operateModel"></param>
        /// <returns></returns>
        //private string GetInsPgsqlLogSql(OperateModel operateModel)
        //{
        //    StringBuilder sql = new StringBuilder();
        //    sql.Append("insert into operation_log(user_id, user_name, ip_addr, station_id, postion_id, operate_detail, log_type, para_data, frequency, real_name) values(")
        //        .Append("'" + operateModel.op_code + "'")
        //        .Append(",'" + operateModel.op_name + "'")
        //        .Append(",'" + operateModel.op_ipaddr + "'")
        //        .Append(",'" + operateModel.deviceInfo.stationCode + "'")
        //        .Append(",'" + operateModel.deviceInfo.position_id + "'")
        //        .Append(",'" + operateModel.logInfo + "'")
        //        .Append("," + operateModel.op_type + "")
        //        .Append(",'" + JsonConvert.SerializeObject(operateModel.parasValues) + "'")
        //        .Append(",'" + operateModel.frequency + "'")
        //        .Append(",'" + operateModel.real_name + "'")
        //        .Append(")");

        //    mLogger.LogInformation(operateModel.op_name + "(realname=" + operateModel.real_name + " userCode=" + operateModel.op_code + ")来自(ip=" + operateModel.op_ipaddr +
        //        ") 对工位号=" + operateModel.deviceInfo.position_id + ("".Equals(operateModel.frequency) ? "" : "频率=" + operateModel.frequency) + " 执行【" + operateModel.logInfo + "】");
        //    return sql.ToString();
        //}

        /// <summary>
        /// GetInsTaskLogSql
        /// </summary>
        /// <param name="operateModel"></param>
        /// <returns></returns>
        private string GetInsTaskLogSql(OperateModel operateModel)
        {
            string sql = "insert into task_detail_log(task_id,oper_key,station_id,position_id,ip_addr,dev_port,cmd_name,op_time,create_time,create_node,memo) values('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}')";
            sql = string.Format(sql, operateModel.task_id, 
                operateModel.oper_key,
                operateModel.deviceInfo.stationCode,
                operateModel.position_id,
                operateModel.deviceInfo.ip_addr,
                operateModel.deviceInfo.port,
                operateModel.cmd_name,
                TransForm.DateTimeFormat(operateModel.op_time),
                TransForm.DateTimeFormat(operateModel.create_time),
                operateModel.create_node,
                operateModel.logInfo
                );

            mLogger.LogInformation(operateModel.op_name + "(realname=" + operateModel.real_name + " userCode=" + operateModel.op_code + ")来自(ip=" + operateModel.op_ipaddr +
                ") 对工位号=" + operateModel.deviceInfo.position_id + ("".Equals(operateModel.frequency) ? "" : "频率=" + operateModel.frequency) + " 执行【" + operateModel.logInfo + "】");
            return sql.ToString();
        }
        #endregion

        #region 电源复位器相关操作
        /// <summary>
        /// 电源复位器重启
        /// </summary>
        private void PowerResetReStart()
        {
            Thread.Sleep(1000);

            IUdpNode udpNode = null;
            string server = paramInfo.GetValueOrDefault("PowerResetAddr", "10.170.1.61");
            string port = paramInfo.GetValueOrDefault("PowerResetPortReStart", "7575");
            while (true)
            {
                try
                {
                    //只需要运行一次，在其中一个节点即可
                    if (nodeNo == 0)
                    {
                        //是否重启电源复位器服务
                        bool reStart = redisPowerData.KeyExists("PowerResetReStart");
                        if (reStart)
                        {
                            if (udpNode == null)
                            {
                                udpNode = new UdpNode();
                            }
                            if (udpNode != null && udpNode.State != ServiceState.Started)
                            {
                                udpNode.Start();
                            }

                            //重启操作有效期
                            DateTime dtReStart = redisPowerData.StringGet<DateTime>("PowerResetReStart");
                            if ((DateTime.Now - dtReStart).TotalSeconds < 10)
                            {
                                if (udpNode != null)
                                {
                                    byte[] cmd = Encoding.Default.GetBytes("ReStationDevServerApp01");
#if DEBUG

#else
                                    udpNode.Send(server, ushort.Parse(port), cmd, cmd.Length);
#endif
                                    mLogger.LogInformation("电源复位器重启");

                                }
                            }

                            redisPowerData.KeyDelete("PowerResetReStart");
                        }
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("电源复位器重启异常，" + e);
                }
                finally
                {
                    Thread.Sleep(5);
                }
            }
        }

        private void PowerCheckConn()
        {
            redisPower.StringSetAsync("PowerResetConnect", 0);
            Thread.Sleep(200);
            while (true)
            {
                try
                {
                    //只需要运行一次，在其中一个节点即可
                    if (nodeNo == 0)
                    {
                        CheckConn();
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("电源复位器重启异常，" + e);
                }
                finally
                {
                    Thread.Sleep(1 * 1000);
                }
            }
        }

        /// <summary>
        /// 电源复位器读取
        /// </summary>
        private void PowerResetRead()
        {
            Thread.Sleep(1000);
            string readStations = "";
            Dictionary<string, DateTime> sedRec = new();
            while (true)
            {
                try
                {
                    //测试
#if DEBUG
                    redisPower.HashSet("PowerResetRead", "0608", new Dictionary<string, object>() { { "datetime", DateTime.Now.AddSeconds(10) }, { "addrs", "150,154,151" } });
#endif

                    //只需要运行一次，在其中一个节点即可
                    if (nodeNo == 0)
                    {
                        //string tmpReadStations = "";
                        ////读取
                        //try
                        //{
                        //    Dictionary<string, Dictionary<string, Object>> readDict = redisPower.HashGetAllDict<Dictionary<string, Object>>("PowerResetRead");
                        //    if (readDict != null && readDict.Count > 0)
                        //    {
                        //        //有效期
                        //        readDict = readDict.Where(d => ((DateTime)d.Value["datetime"]) > DateTime.Now).ToDictionary(d => d.Key, d => d.Value);
                        //        if (readDict.Count > 0)
                        //        {
                        //            int powerStationCountPre = powerStationCount;
                        //            powerStationCount = readDict.Count;
                        //            if (powerStationCountPre == 0)
                        //            {
                        //                CheckConn();
                        //            }

                        //            tmpReadStations = JsonConvert.SerializeObject(readDict.Keys);
                        //            foreach (string stationId in readDict.Keys)
                        //            {
                        //                List<string> addrs = readDict[stationId]["addrs"].ToString().Split(',').ToList();
                        //                foreach (string addr in addrs)
                        //                {
                        //                    string recKey = stationId + "_" + addr;
                        //                    if (!sedRec.ContainsKey(recKey))
                        //                    {
                        //                        sedRec.TryAdd(recKey, DateTime.Now);
                        //                    }

                        //                    if ((DateTime.Now - sedRec[recKey]).TotalMilliseconds > 500)
                        //                    {
                        //                        bool rs = false;
                        //                        for (int i = 0; i < 10; i++)
                        //                        {
                        //                            if (rs)
                        //                            {
                        //                                break;
                        //                            }

                        //                            rs = PowerResetRead(stationId, int.Parse(addr));
                        //                        }
                        //                        Thread.Sleep(2);

                        //                        if (rs)
                        //                        {
                        //                            sedRec[recKey] = DateTime.Now;
                        //                        }
                        //                    }
                        //                }
                        //            }
                        //        }
                        //        else
                        //        {
                        //            powerStationCount = 0;
                        //        }
                        //    }
                        //    else
                        //    {
                        //        powerStationCount = 0;
                        //    }
                        //}
                        //catch (Exception ex)
                        //{
                        //    mLogger.LogError("电源复位器读取异常，" + ex);
                        //}
                        //finally
                        //{
                        //    if (!tmpReadStations.Equals(readStations))
                        //    {
                        //        readStations = tmpReadStations;
                        //        mLogger.LogInformation("电源复位器读取=" + readStations);
                        //    }
                        //}

                        //读取
                        string tmpReadStations = PowerResetRead(sedRec);
                        //记录读取台站变化
                        if (!tmpReadStations.Equals(readStations))
                        {
                            readStations = tmpReadStations;
                            mLogger.LogInformation("电源复位器读取=" + readStations);
                        }

                        //控制
                        PowerResetCtrl(sedRec);
                    }
                    else
                    {
                        powerStationCount = 0;
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("电源复位器下发异常，" + e);
                }
                finally
                {
                    Thread.Sleep(5);
                }
            }
        }

        /// <summary>
        /// 电源复位器读取
        /// </summary>
        /// <param name="sedRec"></param>
        /// <returns></returns>
        private string PowerResetRead(Dictionary<string, DateTime> sedRec)
        {
            string tmpReadStations = "";
            try
            {
                //读取
                Dictionary<string, Dictionary<string, Object>> readDict = redisPower.HashGetAllDict<Dictionary<string, Object>>("PowerResetRead");
                if (readDict != null && readDict.Count > 0)
                {
                    //有效期
                    readDict = readDict.Where(d => ((DateTime)d.Value["datetime"]) > DateTime.Now).ToDictionary(d => d.Key, d => d.Value);
                    if (readDict.Count > 0)
                    {
                        int powerStationCountPre = powerStationCount;
                        powerStationCount = readDict.Count;
                        if (powerStationCountPre == 0)
                        {
                            mLogger.LogInformation("有台站连接，开始连接");
                            CheckConn();
                        }

                        tmpReadStations = JsonConvert.SerializeObject(readDict.Keys);
                        foreach (string stationId in readDict.Keys)
                        {
                            List<string> addrs = readDict[stationId]["addrs"].ToString().Split(',').ToList();
                            foreach (string addr in addrs)
                            {
                                string recKey = stationId + "_" + addr;
                                if (!sedRec.ContainsKey(recKey))
                                {
                                    sedRec.TryAdd(recKey, DateTime.Now);
                                }
                                if (!sedRec.ContainsKey(stationId))
                                {
                                    sedRec.TryAdd(stationId, DateTime.Now);
                                }

                                if ((DateTime.Now - sedRec[recKey]).TotalMilliseconds > 500 && (DateTime.Now - sedRec[stationId]).TotalMilliseconds > 50)
                                {
                                    Thread.Sleep(2);
                                    bool rs = false;
                                    for (int i = 0; i < 10; i++)
                                    {
                                        if (rs)
                                        {
                                            break;
                                        }

                                        rs = PowerResetRead(stationId, int.Parse(addr));
                                    }

                                    if (rs)
                                    {
                                        lock (syncLock)
                                        {
                                            powerSendCount += 1;
                                        }
                                        sedRec[recKey] = DateTime.Now;
                                        sedRec[stationId] = DateTime.Now;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        powerStationCount = 0;
                    }
                }
                else
                {
                    powerStationCount = 0;
                }
            }
            catch (Exception ex)
            {
                mLogger.LogError("电源复位器读取异常，" + ex);
            }

            return tmpReadStations;
        }

        /// <summary>
        /// 电源复位器控制
        /// </summary>
        private void PowerResetCtrl(Dictionary<string, DateTime> sedRec)
        {
            DateTime dtNow = DateTime.Now;
            //控制
            try
            {
                Dictionary<string, DateTime> ctrlDict = redisPower.HashGetAllDict<DateTime>("PowerResetCtrl");
                if (ctrlDict != null && ctrlDict.Count > 0)
                {
                    //直接筛选
                    ctrlDict = ctrlDict.Where(d => d.Value > dtNow).ToDictionary(d => d.Key, d => d.Value);
                    foreach (string stationId in ctrlDict.Keys)
                    {
                        //有效期
                        if (ctrlDict[stationId] > dtNow)
                        {
                            Dictionary<string, Dictionary<string, string>> ctrlInfos = redisPower.HashGetAllDict<Dictionary<string, string>>("PowerResetCtrl_" + stationId);
                            if (ctrlInfos != null && ctrlInfos.Count > 0)
                            {
                                if (!sedRec.ContainsKey(stationId))
                                {
                                    sedRec.TryAdd(stationId, DateTime.Now);
                                }

                                foreach (string key in ctrlInfos.Keys)
                                {
                                    Dictionary<string, string> ctrlInfo = ctrlInfos[key];
                                    string addr = ctrlInfo["addr"];
                                    string param = ctrlInfo["param"];
                                    if (ctrlInfo.ContainsKey("sendTime"))
                                    {
                                        continue;
                                    }

                                    if ((DateTime.Now - sedRec[stationId]).TotalMilliseconds < 50)
                                    {
                                        continue;
                                    }

                                    //经过测试，前后一定要有一定间隔才能确保基本下发成功
                                    //具体原因猜测可能是服务那边处理速度的问题
                                    //发送
                                    Thread.Sleep(2);
                                    bool rs = false;
                                    for (int i = 0; i < 10; i++)
                                    {
                                        if (rs)
                                        {
                                            break;
                                        }

                                        rs = PowerResetCtrl(stationId, int.Parse(addr), param);
                                    }

                                    //控制只下发一次，下发后马上删除
                                    if (rs)
                                    {
                                        lock (syncLock)
                                        {
                                            powerSendCount += 1;
                                        }

                                        //redisPower.HashDeleteAsync("PowerResetCtrl_" + stationId, key);

                                        //不删除，新增发送时间
                                        ctrlInfo.TryAdd("sendTime", TransForm.GetFormatDatetime());
                                        redisPower.HashSetAsync("PowerResetCtrl_" + stationId, key, ctrlInfo);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                mLogger.LogError("电源复位器控制异常，" + ex);
            }
        }

        /// <summary>
        /// 判断打开连接
        /// </summary>
        /// <param name="client"></param>
        /// <param name="server"></param>
        /// <param name="port"></param>
        /// <param name="tag"></param>
        private void CheckConn()
        {
            if (powerStationCount > 0)
            {
                try
                {
                    string conn = redisPower.StringGet("PowerResetConnect") ?? "0";
                    if (powerClient == null)
                    {
                        powerClient = CreateTcpClient(powerServer, int.Parse(powerPort));
                    }
                    else if (powerClient != null && ((powerClient.State != ServiceState.Started && powerClient.State != ServiceState.Starting) || "0".Equals(conn)))
                    {
                        mLogger.LogInformation("powerClient状态：" + powerClient.State + "，重新连接 ...");
                        powerClient.Connect();
                    }

                    if (powerClient.State != ServiceState.Started && powerClient.State != ServiceState.Starting)
                    {
                        mLogger.LogError("powerClient连接异常，状态：" + powerClient.State);
                        redisPower.StringSetAsync("PowerResetConnect", 0);
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("powerClient连接异常，" + e.Message);
                    redisPower.StringSetAsync("PowerResetConnect", 0);
                }
            }
            else
            {
                try
                {
                    if (powerClient != null)
                    {
                        mLogger.LogInformation("无台站连接，主动断开连接");
                        powerClient.Dispose();
                        powerClient = null;
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("powerClient断开异常，" + e.Message);
                }
                finally
                {
                    powerClient = null;
                    redisPower.StringSetAsync("PowerResetConnect", 0);
                }
            }
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="stationId"></param>
        /// <param name="addr"></param>
        /// <returns></returns>
        public bool PowerResetRead(string stationId, int addr)
        {
            bool rs = false;
            try
            {
                HjGetDate hjGetDate = new HjGetDate();

                /****************获取参数****************************************/
                //电源复位器获取参数结构体
                hjGetDate.StationCode = stationId;
                hjGetDate.SystemType = "Restorer";
                hjGetDate.DevAddr = (byte)addr;
                hjGetDate.CmdType = "GetRestorerData";
                hjGetDate.ParamByteCount = 0;

                int len = Marshal.SizeOf(hjGetDate);
                byte[] cmd = TransForm.StructToBytes(hjGetDate);
                //校验时要减去最后两位
                short s = (short)Array.ConvertAll(cmd.Skip(0).Take(len - 2).ToArray(), new Converter<byte, int>(((byte b) => (int)b))).Sum();
                hjGetDate.CheckSum = s;

                cmd = TransForm.StructToBytes(hjGetDate);

                //发送读取命令
                //redisPowerData.StringSet("PowerResetReadTime", DateTime.Now);
                redisPowerData.StringSetAsync("PowerResetReadTime", DateTime.Now);

                if (powerClient != null && powerClient.State == ServiceState.Started)
                {
                    lock (powerClient)
                    {
                        rs = powerClient.Send(cmd, cmd.Length);
                    }
                }
                else
                {
                    mLogger.LogError("电源复位器读取命令下发异常！");
                }
                //mLogger.LogInformation("电源复位器控制读取=" + JsonConvert.SerializeObject(hjGetDate));
                //mLogger.LogInformation("发送 " + TransForm.GetFormatDatetime("HH:mm:ss") + " " + hjGetDate.StationCode + "_" + hjGetDate.DevAddr);
                /****************获取参数****************************************/
            }
            catch (Exception e)
            {
                mLogger.LogError("电源复位器读取命令下发异常，" + e);
            }

            return rs;
        }

        /// <summary>
        /// 控制
        /// </summary>
        /// <param name="stationId"></param>
        /// <param name="addr"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public bool PowerResetCtrl(string stationId, int addr, string param)
        {
            bool rs = false;
            try
            {
                HjYjCmd hjYjCmd = new HjYjCmd();

                /****************控制命令****************************************/
                hjYjCmd.StationCode = stationId;
                hjYjCmd.SystemType = "Restorer";
                hjYjCmd.DevAddr = (byte)addr;
                hjYjCmd.CmdType = "ControlCmd";
                //hjYjCmd.Params = "PowerOn_1"; //PowerOn、PowerOff、Reset
                hjYjCmd.Params = param;
                hjYjCmd.ParamByteCount = 20;

                int len = Marshal.SizeOf(hjYjCmd);
                byte[] cmd = TransForm.StructToBytes(hjYjCmd);
                //校验时要减去最后两位
                short s = (short)Array.ConvertAll(cmd.Skip(0).Take(len - 2).ToArray(), new Converter<byte, int>(((byte b) => (int)b))).Sum();
                hjYjCmd.CheckSum = s;

                cmd = TransForm.StructToBytes(hjYjCmd);

                //控制命令
                if (powerClient != null && powerClient.State == ServiceState.Started)
                {
                    lock (powerClient)
                    {
                        rs = powerClient.Send(cmd, cmd.Length);
                    }

                    if (rs)
                    {
                        mLogger.LogInformation("电源复位器控制命令=" + JsonConvert.SerializeObject(hjYjCmd));
                    }
                }
                else
                {
                    mLogger.LogError("电源复位器控制命令下发异常！");
                }
                /****************控制命令****************************************/
            }
            catch (Exception e)
            {
                mLogger.LogError("电源复位器控制命令下发异常，" + e);
            }

            return rs;
        }

        /// <summary>
        /// 创建TCP侦听 -示例
        /// </summary>
        /// <param name="localAddr">地址</param>
        /// <param name="port">端口</param>
        /// <returns></returns>
        public ITcpClient CreateTcpClient(string addr, int port)
        {
            ITcpClient client = null;
            try
            {
                //TCP客户端
                client = new TcpClient();

                // <2>配置TCPServer的参数
                client.Address = addr;      // IP
                client.Port = (ushort)port;      // 端口
                client.SocketBufferSize = 4096;  // 缓存4K

                // <3> 绑定事件
                client.OnPrepareConnect += OnPrepareConnect;  // 准备连接了事件
                client.OnConnect += OnConnect;                // 连接事件
                client.OnHandShake += OnHandShake;            // TCP握手成功事件
                client.OnSend += OnSend;                      // 数据包发送事件
                client.OnReceive += OnReceive;                // 数据包到达事件
                client.OnClose += OnClose;                    // TCP连接关闭事件

                client.Connect();  // 连接

                mLogger.LogInformation("TcpClient初始化连接：" + addr + ":" + port);
            }
            catch (Exception e)
            {
                client = null;
                mLogger.LogError("TcpClient初始化异常，" + e);
            }
            return client;
        }

        #region 事件
        /// <summary>
        /// 准备连接了事件
        /// </summary>
        /// <param name="sender">客户端</param>
        /// <param name="socket">客户端Id</param>
        /// <returns></returns>
        private HandleResult OnPrepareConnect(HPSocket.IClient sender, IntPtr socket)
        {
            sender.ExtraData = string.Empty;  // 设置附加数据（用来做粘包处理）

            return HandleResult.Ok;
        }

        /// <summary>
        /// 连接事件
        /// </summary>
        /// <param name="sender">客户端</param>
        /// <returns></returns>
        private HandleResult OnConnect(HPSocket.IClient sender)
        {
            try
            {
                mLogger.LogInformation(string.Format("TCP客户端([{0}]{1}:{2})接入TCP服务器{3}:{4}", sender.ConnectionId, sender.BindAddress, sender.BindPort, sender.Address, sender.Port));
                redisPower.StringSetAsync("PowerResetConnect", 1);
            }
            catch { }
            return HandleResult.Ok;
        }

        /// <summary>
        /// TCP握手成功事件
        /// </summary>
        /// <param name="sender">客户端</param>
        /// <returns></returns>
        private HandleResult OnHandShake(HPSocket.IClient sender)
        {
            // 一般用不到
            return HandleResult.Ok;
        }

        /// <summary>
        /// 数据包发送事件
        /// </summary>
        /// <param name="sender">客户端</param>
        /// <param name="data">数据</param>
        /// <returns></returns>
        private HandleResult OnSend(HPSocket.IClient sender, byte[] data)
        {
            //Console.WriteLine(string.Format("TCP客户端发送数据到TCP服务器[{0}]{1}:{2};数据内容[长度{3}]：{4}", sender.ConnectionId, sender.Address, sender.Port, data.Length, Encoding.UTF8.GetString(data)));
            //mLogger.LogInformation(string.Format("TCP客户端发送数据到TCP服务器[{0}]{1}:{2};数据内容[长度{3}]：{4}", sender.ConnectionId, sender.Address, sender.Port, data.Length, Encoding.UTF8.GetString(data)));
            return HandleResult.Ok;
        }

        /// <summary>
        /// 数据包到达事件
        /// </summary>
        /// <param name="sender">客户端</param>
        /// <param name="data">数据</param>
        /// <returns></returns>
        private HandleResult OnReceive(HPSocket.IClient sender, byte[] data)
        {
            // <1> 获取附加数据对象
            if (!(sender.ExtraData is string extraDataStr))
            {
                return HandleResult.Error;
            }

            //校验
            int len = data.Length;
            if (len >= 138)
            {
                //校验时要减去最后两位
                short sum = (short)Array.ConvertAll(data.Skip(0).Take(len - 2).ToArray(), new Converter<byte, int>(((byte b) => (int)b))).Sum();
                short jy = TransForm.ByteToShort(data.Skip(len - 2).Take(2).ToArray());
                if (jy == sum)
                {
                    ChannelState channelState = (ChannelState)TransForm.BytesToStuct(data, typeof(ChannelState));


                    //收到后马上写入缓存
                    redisPowerData.HashSet("PowerResetData_"+ channelState.StationCode, channelState.DevAddr.ToString(), channelState);
                    redisPowerData.StringSet("PowerResetRcvTime", DateTime.Now);
                    redisPowerData.HashSetAsync("PowerResetRcvTime_station", channelState.StationCode + "_" + channelState.DevAddr.ToString(), TransForm.GetFormatDatetime());

                    //mLogger.LogInformation("收到数据=" + JsonConvert.SerializeObject(channelState));
                    //mLogger.LogInformation("收到数据！");
                    //mLogger.LogInformation("收到 " + TransForm.GetFormatDatetime("HH:mm:ss") + " " + channelState.StationCode + "_" + channelState.DevAddr);
                }

                redisPowerData.StringSetAsync("PowerResetConnect", 1);
            }

            return HandleResult.Ok;
        }

        /// <summary>
        /// TCP连接关闭事件
        /// </summary>
        /// <param name="sender">客户端</param>
        /// <param name="socketOperation">关闭类型</param>
        /// <param name="errorCode">错误代码</param>
        /// <returns></returns>
        private HandleResult OnClose(HPSocket.IClient sender, SocketOperation socketOperation, int errorCode)
        {
            try
            {
                sender.ExtraData = null;  // 删除附加数据
                //Console.WriteLine(string.Format("TCP客户端断开与TCP服务器[{0}] {1}:{2}的连接, 断开类型: {3},错误代码: {4}", sender.ConnectionId, sender.Address, sender.Port, socketOperation.ToString(), errorCode));
                mLogger.LogError(string.Format("TCP客户端断开与TCP服务器[{0}] {1}:{2}的连接, 断开类型: {3},错误代码: {4}", sender.ConnectionId, sender.Address, sender.Port, socketOperation.ToString(), errorCode));
                redisPower.StringSetAsync("PowerResetConnect", 0);
            }
            catch { }
            return HandleResult.Ok;
        }
        #endregion 事件
        #endregion

        #region ping方法
        /// <summary>
        /// 用于检查IP地址或域名是否可以使用TCP/IP协议访问(使用Ping命令),true表示Ping成功,false表示Ping失败
        /// </summary>
        /// <param name="strIpOrDName">输入参数,表示IP地址或域名</param>
        /// <returns></returns>
        public bool PingIpOrDomainName(string strIpOrDName)
        {
            bool rs;
            try
            {
                Ping ping = new Ping();
                PingOptions objPinOptions = new PingOptions();
                objPinOptions.DontFragment = true;
                int intTimeout = 120;
                //intTimeout = 1000;
                //intTimeout = 500;
                PingReply pingReply = ping.Send(strIpOrDName, intTimeout, new byte[0], objPinOptions);
                rs = pingReply.Status == IPStatus.Success;
                pingReply.TryDispose();
            }
            catch (Exception)
            {
                rs = false;
            }

            return rs;
        }
        #endregion

        #region 测试下发
        /// <summary>
        /// 测试下发
        /// </summary>
//        private void TaskCmdSend()
//        {
//            Stopwatch sw;
//            while (true)
//            {
//                sw = Stopwatch.StartNew();
//                try
//                {
//                    //获取需要加速的任务
//                    Dictionary<string, Dictionary<string, string>> taskInfo = redisTask.HashGetAllDict<Dictionary<string, string>>("taskInfo");
//                    DateTime dtNow = DateTime.Now;
//                    //ts.TotalSeconds   总秒数
//                    TimeSpan ts = dtNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
//                    long totalSeconds = ts.TotalSeconds.ToLong();

//                    if (taskInfo != null && taskInfo.Count > 0)
//                    {
//                        int orderAllNo = 0;
//                        foreach (string taskId in taskInfo.Keys)
//                        {
//                            //根据副本号分配任务，多个副本各负责一部分
//                            if (orderAllNo % nodeNum == nodeNo)
//                            {
//                                //0临时任务（默认，随时启动），1定时任务（天），2定时任务（周）
//                                string taskType = taskInfo[taskId].ContainsKey("task_type") ? taskInfo[taskId]["task_type"] : "0";
//                                //台站
//                                string stationIds = taskInfo[taskId]["station_ids"];
//                                //设备类型码
//                                string devTypes = taskInfo[taskId]["dev_types"];
//                                //当前运行状态 0-初始化 1-正在运行 2-停止 默认0
//                                string taskSt = taskInfo[taskId]["task_status"];
//                                //使用状态 0-删除 1-在用 默认1
//                                string useType = taskInfo[taskId]["use_type"];
//                                //启动时间
//                                string startTime = taskInfo[taskId]["start_time"];
//                                //持续时长，分
//                                string timerDuration = taskInfo[taskId]["timer_duration"];
//                                //下发间隔，秒
//                                string sendGap = taskInfo[taskId]["send_gap"];

//                                //开始时间
//                                DateTime dtBegin = DateTime.Parse(startTime);

//                                //0临时任务，一次过
//                                if ("0".Equals(taskType))
//                                {

//                                }
//                                //1定时任务（天）
//                                else if ("1".Equals(taskType))
//                                {
//                                    //int days = (dtNow - dtBegin).Days;
//                                    int days = (dtNow.Date - dtBegin.Date).Days;
//                                    //改变日期为当前，以便于后续判断是否持续
//                                    dtBegin = dtBegin.AddDays(days);
//                                }
//                                //2定时任务（周）
//                                else if ("2".Equals(taskType))
//                                {
//                                    //获取星期几，然后从星期数组中获取数据判断
//                                    string weekNoTime = ((int)dtBegin.DayOfWeek).ToString();
//                                    string weekNoNow = ((int)dtNow.DayOfWeek).ToString();
//                                    if (weekNoTime.Equals(weekNoNow))
//                                    {
//                                        //int days = (dtNow - dtBegin).Days;
//                                        int days = (dtNow.Date - dtBegin.Date).Days;
//                                        //改变日期为当前，以便于后续判断是否持续
//                                        dtBegin = dtBegin.AddDays(days);
//                                    }
//                                }

//                                //结束时间
//                                DateTime dtEnd = dtBegin.AddMinutes(int.Parse(timerDuration));

//                                //正在运行状态，时间匹配
//                                if ("1".Equals(taskSt) && dtBegin <= DateTime.Now && dtEnd >= DateTime.Now)
//                                {
//                                    if (totalSeconds % int.Parse(sendGap) == 0)
//                                    {
//                                        List<string> taskStations = stationIds.Split(',').ToList();
//                                        List<string> taskDevTypes = devTypes.Split(',').ToList();
//                                        foreach (string stationId in taskStations)
//                                        { 
//                                            bool stationOffLine = GetStationOffLine(stationId);

//#if DEBUG
//                                            //测试时默认在线，方便测试
//                                            stationOffLine = false;
//#endif

//                                            //台站有设备数据
//                                            if (!stationOffLine && stationPositionDeviceInfos.ContainsKey(stationId) && stationPositionDeviceInfos[stationId].Count > 0)
//                                            {
//                                                foreach (string devType in taskDevTypes)
//                                                {
//                                                    //operateName默认规则，“TEST_”开头，连接设备类型码
//                                                    string operateName = "TEST_" + devType.ToUpper();
//                                                    bool getCmd = false;

//                                                    //筛选符合条件的工位号
//                                                    Dictionary<string, DeviceInfo> matchDeviceInfos = stationPositionDeviceInfos[stationId].Where(d => d.Key.ToUpper().StartsWith(stationId + "_" + devType.ToUpper())).ToDictionary(d => d.Key, d => d.Value);
//                                                    //每个工位号（设备）
//                                                    foreach (string positionId in matchDeviceInfos.Keys)
//                                                    {
//                                                        bool positionOffLine = GetPositionOffLine(positionId);
//#if DEBUG
//                                                        //测试时默认在线，方便测试
//                                                        positionOffLine = false;
//#endif
//                                                        //设备在线才加速
//                                                        if (!positionOffLine && matchDeviceInfos.ContainsKey(positionId))
//                                                        {
//                                                            //设备类型，根据设备类型，判断该设备是否配置了主动获取状态数据命令
//                                                            DeviceInfo deviceInfo = matchDeviceInfos[positionId];
//                                                            List<Dictionary<string, string>> operateInfos = redisTask.HashGet<List<Dictionary<string, string>>>("operateParaModelNew", positionId);
//                                                            if (operateInfos != null && operateInfos.Count > 0)
//                                                            {
//                                                                Dictionary<string, string> operateDetail = null;
//                                                                foreach (Dictionary<string, string> operateInfo in operateInfos)
//                                                                {
//                                                                    //先从操作找
//                                                                    string operate_model_id = operateInfo["operate_model_id"];
//                                                                    Dictionary<string, Dictionary<string, string>> operateDetails = redisTask.HashGet<Dictionary<string, Dictionary<string, string>>>("operations_model", operate_model_id);
//                                                                    if (operateDetails != null && operateDetails.ContainsKey(operateName))
//                                                                    {
//                                                                        operateDetail = operateDetails[operateName];
//                                                                    }
//                                                                    if (operateDetail != null)
//                                                                    {
//                                                                        getCmd = true;

//                                                                        //模板ID
//                                                                        string templateId = operateDetail.ContainsKey("template_id") ? operateDetail["template_id"] ?? "" : "";
//                                                                        //模板ID是否有效
//                                                                        if (issueTemplates.ContainsKey(templateId))
//                                                                        {
//                                                                            //参数明细
//                                                                            Dictionary<string, Dictionary<string, object>> paraDetails = new();
//                                                                            string dftParas = issueTemplates[templateId]["dft_paras"];  //该模板只应该有默认参数
//                                                                            string cmd_text = issueTemplates[templateId]["cmd_text"];
//                                                                            string template_memo = issueTemplates[templateId].ContainsKey("memo") ? issueTemplates[templateId]["memo"] : "";
//                                                                            string[] dftParaArr = issueTemplates[templateId]["dft_paras"].Split(',');
//                                                                            foreach (string para in dftParaArr)
//                                                                            {
//                                                                                if (issuePara.ContainsKey(para))
//                                                                                {
//                                                                                    paraDetails.TryAdd(para, issuePara[para].ToDictionary(d => d.Key, d => (object)d.Value));
//                                                                                }
//                                                                            }

//                                                                            //生成操作模板
//                                                                            OperateModel operateModel = new OperateModel()
//                                                                            {
//                                                                                deviceInfo = deviceInfo,
//                                                                                task_id = int.Parse(taskId),
//                                                                                create_node = nodeName + "_" + (nodeNo + 1).ToString(),
//                                                                                //paras = null,
//                                                                                parasValues = new(),
//                                                                                paraDetails = paraDetails,
//                                                                                cmd_temp = cmd_text,
//                                                                                cmd_paras = dftParas,
//                                                                                cmd_name = operateName,
//                                                                                oper_type = 1,
//                                                                                position_id = positionId,
//                                                                                template_id = templateId,
//                                                                                operate_model_id = operate_model_id,
//                                                                                template_memo = template_memo,
//                                                                                logInfo = "TASKTEST"
//                                                                            };

//                                                                            //主动加速的包，直接写入缓存
//                                                                            lock (rcvCacheTask)
//                                                                            {
//                                                                                rcvCacheTask.Add(operateModel);
//                                                                            }
//                                                                        }

//                                                                        //operateName只要匹配了，就跳出当前循环
//                                                                        break;
//                                                                    }
//                                                                }
//                                                            }
//                                                        }
//                                                    }

//                                                    if (!getCmd)
//                                                    {
//                                                        mLogger.LogError("测试任务，测试操作【"+ operateName+"】未找到，请核查！");
//                                                    }
//                                                }
//                                            }
//                                        }
//                                    }
//                                }
//                            }
//                            orderAllNo++;
//                        }
//                    }
//                }
//                catch (Exception e)
//                {
//                    mLogger.LogError("测试下发异常，" + e);
//                }
//                finally
//                {
//                    int gap = (int)sw.ElapsedMilliseconds;

//                    //减少间隔，提速
//                    int interval = 1000;
//                    Thread.Sleep(gap >= interval ? 1 : (interval - gap));
//                }
//            }
//        }
        #endregion

        #region 台站总览加速
        /// <summary>
        /// 台站总览、频点配置加速
        /// </summary>
        /// <param name="type"></param>
        /// <param name="speedKey"></param>
        /// <param name="devType">需要特殊加速处理的设备类型 特殊处理0x0101信号源状态加速，需要300毫秒超快，这里特殊处理</param>
        private void StationSpeed(string type, string speedKey, bool specFlag = false)
        {
            Stopwatch sw;
            Dictionary<string, DateTime> stationSpeed;
            while (true)
            {
                sw = Stopwatch.StartNew();
                try
                {
                    //获取需要加速的台站
                    //stationSpeed = redis.HashGetAllDict<DateTime>("stationSpeed");
                    stationSpeed = redis.HashGetAllDict<DateTime>(speedKey);
                    if (stationSpeed != null && stationSpeed.Count > 0)
                    {
                        int orderAllNo = 0;
                        //循环需要加速的台站
                        foreach (string stationId in stationSpeed.Keys)
                        {
                            //根据副本号分配任务，多个副本各负责一部分
                            if (orderAllNo % nodeNum == nodeNo)
                            {
                                //判断时间是否还在有效期
                                if (stationSpeed[stationId] > DateTime.Now)
                                {
                                    //在线的台站才进行加速
                                    bool stationOffLine = GetStationOffLine(stationId);
#if DEBUG
                                    //测试时默认在线，方便测试
                                    stationOffLine = false;
#endif
                                    //台站有设备数据
                                    if (!stationOffLine && stationPositionDeviceInfos.ContainsKey(stationId) && stationPositionDeviceInfos[stationId].Count > 0)
                                    {
                                        //每个工位号（设备）
                                        foreach (string positionId in stationPositionDeviceInfos[stationId].Keys)
                                        {
                                            bool positionOffLine = GetPositionOffLine(positionId);
#if DEBUG
                                            //测试时默认在线，方便测试
                                            positionOffLine = false;
#endif
                                            //设备在线才加速
                                            if (!positionOffLine && stationPositionDeviceInfos[stationId].ContainsKey(positionId))
                                            {
                                                //设备类型，根据设备类型，判断该设备是否配置了主动获取状态数据命令
                                                DeviceInfo deviceInfo = stationPositionDeviceInfos[stationId][positionId];

                                                //cmd_type 命令类型，1:状态参数 2:配置参数
                                                //工位号是否配置了主动读取命令
                                                if (devIssueInfo.ContainsKey(deviceInfo.deviceType) && devIssueInfo[deviceInfo.deviceType].ContainsKey(type))
                                                {
#if DEBUG
                                                    //方便测试
#endif

                                                    //特殊处理0x0101信号源状态加速，需要300毫秒超快，这里特殊处理
                                                    //特殊处理时，不包含在specDevice里面的，不执行
                                                    //正常处理时，包含在specDevice里面且type=1的，不执行
                                                    if ((specFlag && !specDevice.ContainsKey(deviceInfo.deviceType)) || (!specFlag && "1".Equals(type) && specDevice.ContainsKey(deviceInfo.deviceType)))
                                                    {
                                                        break;
                                                    }

#if DEBUG
                                                    //方便测试
                                                    //if ("0x0101".Equals(deviceInfo.deviceType))
                                                    //{ 
                                                    
                                                    //}
#endif

                                                    //状态或者配置配置id串
                                                    string[] templateIds = devIssueInfo[deviceInfo.deviceType][type]["template_ids"].Split(',');
                                                    foreach (string templateId in templateIds)
                                                    {
                                                        //模板ID是否有效
                                                        if (issueTemplates.ContainsKey(templateId))
                                                        {
                                                            //参数明细
                                                            Dictionary<string, Dictionary<string, object>> paraDetails = new();
                                                            //参数值
                                                            List<Dictionary<string, string>> dictParasValues = new();
                                                            string dftParas = issueTemplates[templateId]["dft_paras"];  //该模板只应该有默认参数
                                                            string cmd_text = issueTemplates[templateId]["cmd_text"];
                                                            string template_memo = issueTemplates[templateId].ContainsKey("memo") ? issueTemplates[templateId]["memo"] : "";
                                                            string[] dftParaArr = issueTemplates[templateId]["dft_paras"].Split(',');
                                                            foreach (string para in dftParaArr)
                                                            {
                                                                if (issuePara.ContainsKey(para))
                                                                {
                                                                    paraDetails.TryAdd(para, issuePara[para].ToDictionary(d => d.Key, d => (object)d.Value));
                                                                }
                                                            }

                                                            //判断参数是否有需要动态填充的
                                                            foreach (string para in paraDetails.Keys)
                                                            {
                                                                Dictionary<string, string> paraDetail = paraDetails[para].ToDictionary(d => d.Key, d => d.Value.ToString());
                                                                //是否必须前台输入的参数
                                                                if (paraDetail.ContainsKey("is_need") && bool.Parse(paraDetail["is_need"]))
                                                                {
                                                                    //这里暂时只能处理频率字段
                                                                    if (paraDetail.ContainsKey("para_code") && "FREQUENCY".Equals(paraDetail["para_code"].ToUpper()))
                                                                    {
                                                                        //从redis缓存尝试获取频率信息
                                                                        if (positionIdToFrqs.ContainsKey(deviceInfo.stationCode) && positionIdToFrqs[deviceInfo.stationCode].ContainsKey(deviceInfo.position_id))
                                                                        {
                                                                            foreach (string frq in positionIdToFrqs[deviceInfo.stationCode][deviceInfo.position_id].Keys)
                                                                            {
                                                                                dictParasValues.Add(new() { { para, frq } });
                                                                            }
                                                                        }
                                                                    }
                                                                }
                                                            }

                                                            //多个频率时循环每一个频率依次下发
                                                            if (dictParasValues.Count > 0)
                                                            {
                                                                foreach (var parasValues in dictParasValues)
                                                                {
                                                                    //生成操作模板
                                                                    OperateModel operateModel = new OperateModel()
                                                                    {
                                                                        deviceInfo = deviceInfo,
                                                                        create_node = nodeName + "_" + (nodeNo + 1).ToString(),
                                                                        //paras = null,
                                                                        parasValues = parasValues,
                                                                        paraDetails = paraDetails,
                                                                        cmd_temp = cmd_text,
                                                                        cmd_paras = dftParas,
                                                                        position_id = positionId,
                                                                        template_id = templateId,
                                                                        template_memo = template_memo,
                                                                        logInfo = "QUICK"
                                                                    };

                                                                    //主动加速的包，直接写入缓存
                                                                    lock (rcvCacheQuick)
                                                                    {
                                                                        rcvCacheQuick.Add(operateModel);
                                                                    }
                                                                }
                                                            }
                                                            else
                                                            {
                                                                //生成操作模板
                                                                OperateModel operateModel = new OperateModel()
                                                                {
                                                                    deviceInfo = deviceInfo,
                                                                    create_node = nodeName + "_" + (nodeNo + 1).ToString(),
                                                                    //paras = null,
                                                                    parasValues = new(),
                                                                    paraDetails = paraDetails,
                                                                    cmd_temp = cmd_text,
                                                                    cmd_paras = dftParas,
                                                                    position_id = positionId,
                                                                    template_id = templateId,
                                                                    template_memo = template_memo,
                                                                    logInfo = "QUICK"
                                                                };

                                                                //主动加速的包，直接写入缓存
                                                                lock (rcvCacheQuick)
                                                                {
                                                                    rcvCacheQuick.Add(operateModel);
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    //不在有效期的，移除
                                    //redis.HashDelete("stationSpeed", stationId);
                                    redis.HashDelete(speedKey, stationId);
                                }
                            }
                            orderAllNo++;
                        }
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("台站加速异常，" + e);
                }
                finally
                {
                    int gap = (int)sw.ElapsedMilliseconds;

                    //减少间隔，提速
                    int interval = specFlag ? 300 : 1000;
                    Thread.Sleep(gap >= interval ? 1 : (interval - gap));
                }
            }
        }

        /// <summary>
        /// 判断台站是否离线
        /// </summary>
        /// <param name="checkKey"></param>
        /// <returns></returns>
        private bool GetStationOffLine(string stationId)
        {
            //是否离线
            //string val = mRedisCache.GetString("online_" + stationId);
            string val = redis.StringGet("online_" + stationId);
            if (val != null && "0".Equals(val))
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 获取工位号在线状态
        /// </summary>
        /// <param name="positionId"></param>
        /// <returns></returns>
        private bool GetPositionOffLine(string positionId)
        {
            //是否离线
            //string val = mRedisCache.GetString("online_" + positionId);
            string val = redis.StringGet("online_" + positionId);
            //获取不到值也判断为离线
            if (val != null && "0".Equals(val))
            {
                return false;
            }
            return true;
        }
        #endregion

        #region 命令生成
        /// <summary>
        /// 数据处理 生成下发命令
        /// </summary>
        private void DealData(BlockingCollection<OperateModel> rcvCache)
        {
            Thread.Sleep(1000);
            OperateModel operateModel = null;
            while (true)
            {
                try
                {
                    while (rcvCache.Count > 0)
                    {
                        Thread.Sleep(10);
                        lock (rcvCache)
                        {
                            operateModel = rcvCache.Take();
                        }

                        //升级后的命令生成方式
                        //GetCommand(operateModel);
                        operateModel = CommandUtil.GetCommand(operateModel, mLogger);

                        if (operateModel.cmd != null && !"".Equals(operateModel.cmd))
                        {
                            //操作结构体提交到Kafka上
                            //ThreadPool.QueueUserWorkItem(new WaitCallback(PublishData), operateModel);
                            //if (!"QUICK".Equals(operateModel.logInfo) && !"SNMP".Equals(operateModel.logInfo))
                            //{
                            //    System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(PublishData), operateModel);
                            //}
                            //else
                            //{
                            //    System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(PublishDataQuick), operateModel);
                            //}

                            //20250528台站端，改为通过redis发布

                            //snmp和加速的的不写日志了
                            //oper_type默认为0，非0的是测试数据
                            //非加速QUICK和SNMP的，测试数据，特殊处理
                            //if (!"QUICK".Equals(operateModel.logInfo) && !"SNMP".Equals(operateModel.logInfo) && operateModel.oper_type == 0)
                            //{
                            //    //ThreadPool.QueueUserWorkItem(new WaitCallback(PublishSqlData), new Dictionary<string, string>() { { "dbtype", "0" }, { "sql", GetInsLogTaosSql(operateModel) } });
                            //    //ThreadPool.QueueUserWorkItem(new WaitCallback(PublishSqlData), new Dictionary<string, string>() { { "dbtype", "1" }, { "sql", GetInsPgsqlLogSql(operateModel) } });
                            //}
                            ////测试任务
                            //else if ("TASKTEST".Equals(operateModel.logInfo) && operateModel.oper_type == 1)
                            //{
                            //    //写task_detail_log表
                            //    string sql = GetInsTaskLogSql(operateModel);
                            //    string kafkakey = operateModel.deviceInfo.position_id;
                            //    System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(PublishSqlData), new Dictionary<string, string>() { { "dbtype", "1" }, { "key", kafkakey }, { "sql", sql } });
                            //}

                            //if (operateModel.oper_type != 0)
                            //{
                            //    mLogger.LogInformation("往" + operateModel.position_id + "发送一条测试命令，oper_key=" + operateModel.oper_key);
                            //}
                        }
                    }
                }
                catch (Exception e)
                {
                    mLogger.LogError("命令生成或写数据库日志异常，" + e);
                    mLogger.LogError("异常指令，" + (operateModel == null ? "" : JsonConvert.SerializeObject(operateModel)));
                }
                finally
                {
                    Thread.Sleep(10);
                }
            }
        }
        #endregion

        #region 打印日志
        /// <summary>
        /// 定时监视
        /// </summary>
        private void TimerTask()
        {
            long numsRcvPre = 0;    //上次数量
            long cmdSendPre = 0;    //上次数量
            int times = 0;
            while (true)
            {
                if ((numsRcv - numsRcvPre) > 0 || (cmdSend - cmdSendPre) > 0 || timeOutNum > 0 || powerSendCount > 0)
                {
                    mLogger.LogInformation(nodeName + " 接收命令=" + (numsRcv - numsRcvPre) + " 下发命令=" + (cmdSend - cmdSendPre) + " 提交超时=" + timeOutNum +
                        " 电源复位器发送命令=" + powerSendCount +
                        " 节点数=" + nodeNum + " 当前节点号=" + (nodeNo + 1));
                }
                else
                {
                    mLogger.LogInformation(nodeName + " 节点数=" + nodeNum + " 当前节点号=" + (nodeNo + 1));
                }
                numsRcvPre = numsRcv;
                cmdSendPre = cmdSend;

                lock (syncLock)
                {
                    timeOutNum = 0;
                    powerSendCount = 0;
                }

                if (times >= 60)
                {
                    times = 0;
                }

                times++;
                Thread.Sleep(10 * 1000);
            }
        }
        #endregion

        /// <summary>
        /// 心跳服务，定时往redis发送心跳数据
        /// 用于获取当前服务副本数量
        /// </summary>
        private void HeartBeatTask()
        {
            //当前节点ID 即UUID
            string uuid = Guid.NewGuid().ToString("N");
            string keySvr = "CommandIssueService";   //心跳节点名称，保证唯一
            string lockKey = "lockKeyCommandIssueService";   //心跳节点锁key，保证唯一
            int gapSec = 2; //循环判断间隔
            int gapCheck = 20;   //判断时间差间隔，适当大于gapSec，如果大于，则判断为该节点离线，移除掉

            string server = paramInfo.GetValueOrDefault("PowerResetAddr", "10.170.1.61");

            //每次启动清空
            Thread.Sleep(new Random().Next(1, (gapSec + 1)) * 1000);
            while (true)
            {
                try
                {
                    //更新自身节点最新时间
                    redis.HashSet(keySvr, uuid, DateTime.Now);

                    //判断副本数最新时间 锁过期时间为5秒
                    redis.LockAndDo(lockKey, uuid, TimeSpan.FromSeconds(5),
                        () =>
                        {
                            //获取key下所有节点及其时间
                            Dictionary<string, DateTime> heartInfo = redis.HashGetAllDict<DateTime>(keySvr);
                            if (heartInfo != null && heartInfo.Count > 0)
                            {
                                //判断节点是否超时gapCheck，超时则移除
                                //判断当前节点ID再心跳节点的位置
                                int idx = 0;
                                foreach (string node in heartInfo.Keys)
                                {
                                    if (uuid.Equals(node))
                                    {
                                        //节点ID位置
                                        nodeNo = idx;
                                    }

                                    //判断节点过期时间
                                    if ((DateTime.Now - heartInfo[node]).TotalSeconds > gapCheck)
                                    {
                                        redis.HashDelete(keySvr, node);
                                    }
                                    else
                                    {
                                        idx++;
                                    }
                                }

                                //获得节点数
                                nodeNum = (idx == 0) ? 1 : idx;
                            }
                            return true;
                        });

                    //顺带进行ping服务
                    if (nodeNo == 0)
                    {
                        string conn = redisPower.StringGet("PowerResetConnect") ?? "0";
                        if ("1".Equals(conn))
                        { 
                            //如果ping不通，就默认置0
                            bool rs = PingIpOrDomainName(server);
                            if (!rs)
                            {
                                mLogger.LogError("电源复位器服务器无法ping通：" + server);
                                redisPower.StringSetAsync("PowerResetConnect", 0);
                            }
                        }
                    }
                }
                finally
                {
                    Thread.Sleep(gapSec * 1000);
                }
            }
        }

        /// <summary>
        /// 启动任务
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            mLogger.LogInformation("服务已启动");

            new Thread(TimerInit).Start();
            //new Thread(TaskCmdSend).Start();   //测试下发

            //new Thread(PowerCheckConn).Start();   //电源复位器读取、控制
            //new Thread(PowerResetRead).Start();   //电源复位器读取、控制
            //new Thread(PowerResetReStart).Start();   //电源复位器服务重启

            ////new Thread(StationSpeed).Start();   //台站加速
            new Thread((ThreadStart)(delegate { StationSpeed("1", "stationSpeed"); })).Start();    //总览状态数据加速
            new Thread((ThreadStart)(delegate { StationSpeed("1", "stationSpeed", true); })).Start();    //总览状态数据加速
            new Thread((ThreadStart)(delegate { StationSpeed("2", "stationSpeedCfg"); })).Start();    //频点等配置加速
            //new Thread(DealData).Start();   //数据处理线程
            //new Thread((ThreadStart)(delegate { DealData(rcvCacheNormal); })).Start();
            new Thread((ThreadStart)(delegate { DealData(rcvCacheQuick); })).Start();
            //new Thread((ThreadStart)(delegate { DealData(rcvCacheTask); })).Start();
            new Thread(HeartBeatTask).Start();
            new Thread(TimerTask).Start();
            return Task.CompletedTask;
        }
    }
}
